const path = require('path');
const fs = require('fs');
const vscode = require('vscode');

const { Defer, confirmDir, undef, info, asyncSleep, isWin } = require('../utils.js');
const { basePath, workSpacePath } = require('../base.js');

const w = require('vscode').window, ws = vscode.workspace;

async function holdLayout() {
    /**
     * 设置layout为左7右三
     */
    await vscode.commands.executeCommand(
        'vscode.setEditorLayout',
        { orientation: 0, groups: [{ groups: [{}], size: 0.7 }, { groups: [{}], size: 0.3 }] }
    );
}

/**
 * @param {*} serviceHandler 
 */
function contributeServicesForEditor(serviceHandler) {
    const {
        subscriptions,
        getRelaPathFromWebView,
        statusBarHandler,
        panelHandler } = serviceHandler.context;

    /**
     * medev virtual documents文件
     * 虚拟path(不带scheme)-->content
     */
    const mvdFiles = {}

    const vdProvider = {
        onDidChangeEmitter: new vscode.EventEmitter(),
        provideTextDocumentContent(uri) {
            return mvdFiles[uri.path] || '';
        }
    }
    //不加这个属性,onDidChangeEmitter.fire(targetUri)触发更新是无效的
    vdProvider.onDidChange = vdProvider.onDidChangeEmitter.event;

    subscriptions.push(ws.registerTextDocumentContentProvider('mvd', vdProvider));


    /**
     * socketI-->editor的映射关系
     */
    const editorMap = (function () {
        const _map = {};
        function _editorId(editor) {
            for (let sId in _map) {
                const _editor = _map[sId];
                if (_editor === editor) return sId;
            }
        }
        return {
            unbindEditor(editor) {
                let deleteId = _editorId(editor);
                if (deleteId) delete _map[deleteId];
            },

            bindEditor(socketId, editor) {
                /**
                 * 对于同一个editor,去掉以前的绑定,并绑定到新的socketId上
                 * 这样一个editor只对应唯一一个socketId
                 */
                this.unbindEditor(editor);
                _map[socketId] = editor;
            },

            getBindedEditor(socketId) {
                return _map[socketId];
            }
        }
    })();


    /**
     * editor-->目标文件路径之间的映射
     */
    const editorTargetMap = new WeakMap();

    /**
     * 无论有多少个webview,全局同时只能有一个editor被打开
     */
    const editorHandler = (function () {
        let willCloseFile;
        let closeDefer;

        /** 
         * 当前正在编辑(和webIde建立关系)的editor,但不一定是active的
         * @type {vscode.TextEditor}
         */
        let editingEditor;
        /**
         * 曾经编辑过的文件路径
         * @todo 以后这些文件会改成虚拟文件
         */
        const deprecatedEditedPaths = new Set();

        /**
         * @type {Function}
         */
        let editingReply;


        /**
         * 配合{@link closeEditor}方法,将切换过来的willCloseFile editor关掉
         * 当切换到webview的时候,textEditor会返回undefined
         */
        w.onDidChangeActiveTextEditor(function (textEditor) {
            /**
             * 查看是否有需要关闭的editor
             */
            if (willCloseFile && willCloseFile === textEditor.document.fileName) {
                willCloseFile = null;
                vscode.commands.executeCommand('workbench.action.closeActiveEditor').then(_ => {
                    if (closeDefer) {
                        closeDefer.resolve(true);
                    }
                })
            }
        });

        /**
         * 当对vscode编辑的代码进行保存时,将修改过的代码回传到请求端(回调)
         */
        ws.onDidSaveTextDocument(textDocument => {
            if (!editingEditor) {
                //当前选中组件已经改变，编辑无效
                return;
            }

            if (editingEditor.document === textDocument) {
                /**
                  * 让所编辑的webview处于active的状态
                  * @todo:vscode出现bug,使得让webviewPanel显示这一下出问题(但在其他时候触发是没有问题的),让当前编辑器也跑到column.one的位置(后续还有其他问题),暂时注释掉
                  */
                //const targetRelaPath = editorTargetMap.get(editingEditor);
                //await panelHandler.activePanel(targetRelaPath);

                setTimeout(async () => {
                    /** 
                     * 向ws客户端返回编辑的结果 
                     */
                    try {
                        editingReply(editingEditor.document.getText());
                    } catch (e) {
                        console.error(e);
                    }
                    /**
                     * 让输入焦点重回这个编辑器,支持用户持续修改
                     */
                    //vscode.commands.executeCommand('workbench.action.focusPreviousGroup');
                    await vscode.window.showTextDocument(textDocument, { viewColumn: vscode.ViewColumn.Two, preview: true });

                    /**
                     * 让所编辑的webview处于active的状态
                     */
                    const targetRelaPath = editorTargetMap.get(editingEditor);
                    await panelHandler.activePanel(targetRelaPath);
                }, 0);
            } else if (deprecatedEditedPaths.has(textDocument.fileName)) {
                setTimeout(() => {
                    deprecatedEditedPaths.delete(textDocument.fileName);
                    vscode.commands.executeCommand('workbench.action.closeActiveEditor');
                    w.showWarningMessage('无效的编辑，请从属性编辑窗口选择要编辑的属性');
                }, 0);
            }
        });

        return {
            setEditing(editor, reply) {
                editingEditor = editor;
                editingReply = reply;
            },

            getEditingEditor() {
                return editingEditor;
            },

            addDeprecatedEditedPath(p) {
                deprecatedEditedPaths.add(p)
            },
            /**
             * 关闭一个mx dev打开的editor
             * @param {vscode.TextEditor} editor 
             */
            async closeEditor(editor = editingEditor) {
                if (editor) {
                    if (w.activeTextEditor && w.activeTextEditor === editor) {
                        await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
                    } else {
                        willCloseFile = editor.document.fileName;
                        closeDefer = new Defer();
                        /**
                         * 先active这个editor,再关闭,这样操作会造成闪烁
                         * mx dev打开的editor都在vscode.ViewColumn.Two的位置
                         */
                        await vscode.commands.executeCommand('vscode.open', editor.document.uri, vscode.ViewColumn.Two);
                        await closeDefer.promise;
                        closeDefer = null;
                        //
                        editor === editingEditor && (editingEditor = null);
                    }

                    editorMap.unbindEditor(editor);

                    return editor;
                }
            },

            /**
            * 结束上一次编辑请求的回调
            * @param {boolean} shouldCloseEditor
            */
            async closeLastEditing(shouldCloseEditor = true) {
                /**
                 * 客户端每回调一次,就结束上一次的调用
                 */
                if (editingReply != null) {
                    try {
                        /**
                         * 结束上一次的调用(空参数调用表示结束请求)
                         */
                        editingReply();
                    } catch (e) {
                        console.info(e);
                    }
                    editingReply = null;
                    /**
                     * 结束上一个编辑器,否则用户可能会切换回上一个编辑器继续编辑,但这个编辑已经close是无效的
                     */
                    if (shouldCloseEditor) {
                        await this.closeEditor(editingEditor)
                    }
                    editingEditor = null;
                }
            },

            /**
             * 打开vscode编辑器
             */
            async openEditor(targetFile) {
                const document = await vscode.workspace.openTextDocument(vscode.Uri.parse(targetFile));
                /**
                 * vscode对于同一个文件,使用同一个editor对象
                 * 不同的文件,使用不同的editor
                 * preview = true,当打开一个新的editor时,会覆盖原来这个editor,不打开新的editor.如果需要打开新的editor,将preview设为false即可
                 */
                const editor = await vscode.window.showTextDocument(document, { viewColumn: vscode.ViewColumn.Two, preview: true });

                return editor;
            },

            /**
             * ws关闭时,关闭其对应的editor,其实不关也没什么
             */
            async closeEditorOnWsCLose(socketId) {
                /**
                 * @type {vscode.TextEditor}
                 */
                const editor = editorMap.getBindedEditor(socketId);
                if (editor) {
                    if (editingEditor === editor) {
                        await this.closeLastEditing();
                    } else {
                        await this.closeEditor(editor);
                    }
                }
            }
        }

    })();


    serviceHandler.regsiterAsyncService({
        /** 
         * 接收从webIDE端传来的代码,并应用 
         * @param {String} code
         *     ide端回传的代码
         * @param {String }mode
         *     取值为overlap(写回target文件)|compare(比较),
         * 当targetRelaPath为demo时,仅仅展示代码 
         * @param reply
         */
        async applyCode(code, mode, reply) {
            /**
             * 要被更新的文件
             */
            let updateFile;
            const targetRelaPath = getRelaPathFromWebView(reply);
            if (typeof targetRelaPath === 'string') {
                if (mode === 'overlap') {
                    //覆盖
                    updateFile = path.join(workSpacePath, targetRelaPath);
                    //写入文件
                    fs.writeFileSync(updateFile, code);
                    isWin && (updateFile = 'file:/' + updateFile);
                }
            } else {
                mode = 'demo';
            }

            if (!updateFile) {
                updateFile = './~result.vue';
                mvdFiles[updateFile] = code;
                updateFile = 'mvd:' + updateFile;
                //通知系统,更新虚拟文件
                vdProvider.onDidChangeEmitter.fire(vscode.Uri.parse(updateFile));
            }

            info('updateFile', updateFile);

            if (mode === 'compare') {
                /**
                 * 比较两个文件
                 */
                await vscode.commands.executeCommand("vscode.diff",
                    vscode.Uri.parse(updateFile),
                    vscode.Uri.file(path.join(workSpacePath, targetRelaPath)), '请手动从左边合并代码到右边');
            } else {
                await holdLayout();
                await asyncSleep(150);
                /**
                 *  打开一个editor用于展示此文件(如果是demo,编辑功能无效)
                 */
                await editorHandler.openEditor(updateFile);
            }

            //空参数调用,表示结束请求
            reply();
        },

        /**
         * 请求编辑一段代码片段,插件会打开一个编辑器
         * @param {String} code 
         * @param {String} type 
         * @param {function} reply 
         */
        async requestForEdit(code, type, reply) {
            statusBarHandler.actionMsg = '打开要编辑的文件';
            statusBarHandler.showStatusMsg();

            if (undef(code)) code = '';

            //info('type:', type, 'code', code);
            type = type.toLowerCase();
            let targetFile;
            if (type === 'object' || type === 'function' || type === 'array' || type === 'js') {
                targetFile = path.join(basePath, 'editing.js');
            } else if (type === 'css' || type === 'style' || type === 'style-css') {
                targetFile = path.join(basePath, 'editing.css');
            } else if (type === 'style-less') {//增加对less的支持
                targetFile = path.join(basePath, 'editing.less');
            } else if (type === 'html' || type === 'template') {
                targetFile = path.join(basePath, 'editing.html');
            }
            //确保文件所在目录已经创建
            confirmDir(targetFile);
            //将要编辑的代码写入文件
            fs.writeFileSync(targetFile, code);

            const lastEditing = editorHandler.getEditingEditor();
            /**
             * 因为用户在ide中会切换组件,所以打开一个新的editor后,要编辑的目标组件变了
             * 所以必须结束上一次的编辑
             * 因此,实际只有一个editor(最多有一个editor,而且是editingEditor,还可能没有editor)
             */
            await editorHandler.closeLastEditing(false);

            await holdLayout();
            /**
             * 打开文件,待返回时设置当前编辑器
             */
            const editor = await editorHandler.openEditor((isWin ? 'file:/' : '') + targetFile);
            //将旧的,没有关闭的editor加入到EditedEditor
            if (lastEditing && lastEditing !== editor) {
                editorHandler.addDeprecatedEditedPath(lastEditing.document.fileName);
            }

            //设置正在编辑的editor和返回方法
            editorHandler.setEditing(editor, reply);

            //获取sockte参数
            const socketId = reply['__socketId'];
            //将socketId关联新打开的editor
            editorMap.bindEditor(socketId, editor);

            //获取从webView传过来的设计文件的相对路径
            const targetRelaPath = getRelaPathFromWebView(reply);
            //将editor和设计的文件相关联
            editorTargetMap.set(editor, targetRelaPath);

            statusBarHandler.actionMsg = null;
            statusBarHandler.showStatusMsg();

            await asyncSleep(300);
            await holdLayout();
        },

        /**
         * ws连接断掉时的回调
         */
        async onconnectionclose(socketId) {
            await editorHandler.closeEditorOnWsCLose(socketId);
        },

        /**
         * @todo 直接关闭editor容易引起webview崩溃,考虑将editor设一个标志位即不允许编辑的标志位,如果编辑了就提示无效或者干脆不让保存,或者再关闭
         * propsGridEditor切换active control的时候
         * 以前在webview关闭的时候也会关闭,现在已经取消
         */
        async closeEditing() {
            //属性编辑器已经刷新,当前editing已经无效,关闭
            // setTimeout(async ()=>{
            //     await editorHandler.closeEditor() && await holdLayout();
            // },300);
            editorHandler.closeLastEditing(false);
        },

    });
}

module.exports = { contributeServicesForEditor }